﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tesla.Graphics;
using Tesla.Math;
using Tesla.Content;
using Tesla.Scene.Shape;
using Tesla.Scene;

namespace TeslaSamples.Effects {
    [AppDescription("Glow Effect", PlatformCapabilities.Direct3D10 | PlatformCapabilities.XNA4, "GlowImage", "GlowDesc", "GlowSource")]
    public class GlowSample : BasicApp {
        private Spatial ship;
        private Material engineMat;
        private RenderTarget2D rt1;
        private RenderTarget2D rt3;
        private RenderTarget2D sceneRT;
        private int width;
        private int height;
        private RenderTarget2D rt2;
        private Effect blurEffect;
        private SpriteBatch batch;
        private float blurAmount = 25f;
        private Box sky;

        protected override void LoadContent() {
            ModelLoaderParameters mlp = new ModelLoaderParameters();
            mlp.SwapWindingOrder = true;
            mlp.NormalGeneration = NormalGeneration.Face;

            mlp.PreferLitMaterials = true;
            mlp.GenerateTangentBasis = false;
            ship = ContentManager.Load<Spatial>("Models\\station.dxs", mlp);

            ship.AddController(new Tesla.Scene.Extension.RotateController(new Vector3(0, 1, 0), 5));
            ship.SetRenderState(SamplerState.AnisotropicWrap);
            engineMat = ContentManager.Load<Material>("BasicTexture.tem");

            Material mat = new Material();
            mat.LoadEffect(ContentManager.Load<Effect>("Shaders//TextureGlowEffect.fx"));
            mat.SetActiveTechnique("TextureGlow");
            mat.SetParameter("DiffuseMap", ContentManager.Load<Texture2D>("Textures//station_diff.dds"));
            mat.SetParameter("EmissiveMap", ContentManager.Load<Texture2D>("Textures//station_glow.dds"));
            mat.SetParameter("MatEmissive", new Vector3(.5f, .5f, .5f));
            mat.SetEngineParameter("WVP", Tesla.Core.EngineValue.WorldViewProjection);
            mat.SetEngineParameter("World", Tesla.Core.EngineValue.WorldMatrix);
            mat.SetEngineParameter("WorldIT", Tesla.Core.EngineValue.WorldInverseTranspose);
            mat.SetEngineParameter("EyePos", Tesla.Core.EngineValue.CameraPosition);
           
            ship.SetMaterial(mat);

            engineMat.SetParameter("DiffuseMap", ContentManager.Load<Texture2D>("Textures//station_glow.dds"));

            width = Renderer.CurrentCamera.Viewport.Width;
            height = Renderer.CurrentCamera.Viewport.Height;

            bool test = Renderer.Adapter.QueryRenderTargetFormat(SurfaceFormat.Color, DepthFormat.Depth24Stencil8, 4);

            sceneRT = new RenderTarget2D(width, height, false, Window.SwapChain.PresentationParameters.BackBufferFormat, Window.SwapChain.PresentationParameters.DepthStencilFormat, Window.SwapChain.PresentationParameters.MultiSampleCount, RenderTargetUsage.DiscardContents);
            rt1 = new RenderTarget2D(width , height , true, sceneRT.Format, DepthFormat.None);
            rt2 = new RenderTarget2D(width , height , true, sceneRT.Format, DepthFormat.None);
            rt3 = new RenderTarget2D(width, height, true, sceneRT.Format, DepthFormat.None);
            blurEffect = ContentManager.Load<Effect>("Shaders//GaussianBlur.fx");
            batch = new SpriteBatch();

            RootNode.AddChild(ship);
            sky = new Box("Space", Vector3.Zero, 100, 100, 100);
            sky.SceneHints.RenderBucketType = RenderBucketType.Skip;
            sky.Material = ContentManager.Load<Material>("Materials//SpaceSkybox.tem");

            RootNode.SetModelBound(new Tesla.Bounding.BoundingBox());
        }
        protected override void OnUpdate(Tesla.Core.GameTime time) {
            sky.Update(time);
        }
        
        protected override void Render(IRenderer renderer) {
            Window.SwapChain.Clear(Color.Black);

            Viewport viewport = renderer.CurrentCamera.Viewport;
            float invWidth = (viewport.Width > 0) ? (1f / ((float) viewport.Width)) : 0f;
            float invHeight = (viewport.Height > 0) ? (-1f / ((float) viewport.Height)) : 0f;
            Matrix matrix = new Matrix();
            matrix.M11 = invWidth * 2f;
            matrix.M22 = invHeight * 2f;
            matrix.M33 = 1f;
            matrix.M44 = 1f;
            matrix.M41 = -1f;
            matrix.M42 = 1f;
            matrix.M41 = matrix.M41 - invWidth;
            matrix.M42 = matrix.M42 - invHeight;

            Matrix projection = Matrix.CreateOrthoMatrix(0, viewport.Width, viewport.Height, 0, 0, 1);
            blurEffect.Parameters["SpriteTransform"].SetValue(projection);

            Viewport rtViewport = new Viewport(0, 0, width, height);
            renderer.Viewport = rtViewport;

            //Pass 1 - Draw scene's engine effects
            renderer.SetRenderTarget(sceneRT);
            renderer.EnforcedMaterial = engineMat;
            RootNode.Draw(renderer);
            renderer.RenderQueue.SortRenderClear();
            renderer.EnforcedMaterial = null;

            //Pass 2 - Draw from scene into RT1, blur horizontally
            renderer.EnforcedRasterizerState = RasterizerState.CullNone;
            renderer.SetRenderTarget(rt1);
            SetBlurEffectParameters(1.0f / (float)rt1.Width, 0);
            DrawFullScreenQuad(sceneRT, BlendState.Opaque, rt1.Width, rt1.Height, blurEffect);

            //Pass 3 - Draw from RT1 to RT2, blur vertically
            renderer.SetRenderTarget(rt2);
            SetBlurEffectParameters(0, 1.0f / (float)rt1.Height);
            DrawFullScreenQuad(rt1, BlendState.Opaque, rt2.Width, rt2.Height, blurEffect);

            //Draw scene, and layer rt1 over it
            renderer.Viewport = viewport;
            renderer.SetRenderTarget(null);
            sky.Draw(renderer);
            RootNode.Draw(renderer);
            renderer.RenderQueue.SortRenderClear();

        //    DrawFullScreenQuad(sceneRT, BlendState.AdditiveBlend, width, height, null);

            //Pass 4 - Draw blurred image to screen
            renderer.Viewport = rtViewport;
            DrawFullScreenQuad(rt2, BlendState.AdditiveBlend, width, height, null);
            renderer.Viewport = viewport;
            Window.SwapChain.Present();
        }

        private void DrawFullScreenQuad(Texture2D texture, BlendState bs, int width, int height, Effect customEffect) {
            batch.Begin(SpriteSortMode.Immediate, bs, null, null, null, customEffect);
            batch.Draw(texture, new Rectangle(0, 0, width, height), Color.White);
            batch.End();
        }

        /// <summary>
        /// Computes sample weightings and texture coordinate offsets
        /// for one pass of a separable gaussian blur filter.
        /// </summary>
        private void SetBlurEffectParameters(float dx, float dy) {
            // Look up the sample weight and offset effect parameters.
            IEffectParameter weightsParameter, offsetsParameter;

            weightsParameter = blurEffect.Parameters["SampleWeights"];
            offsetsParameter = blurEffect.Parameters["SampleOffsets"];

            // Look up how many samples our gaussian blur effect supports.
            int sampleCount = weightsParameter.Elements.Count;

            // Create temporary arrays for computing our filter settings.
            float[] sampleWeights = new float[sampleCount];
            Vector2[] sampleOffsets = new Vector2[sampleCount];

            // The first sample always has a zero offset.
            sampleWeights[0] = ComputeGaussian(0);
            sampleOffsets[0] = new Vector2(0);

            // Maintain a sum of all the weighting values.
            float totalWeights = sampleWeights[0];

            // Add pairs of additional sample taps, positioned
            // along a line in both directions from the center.
            for(int i = 0; i < sampleCount / 2; i++) {
                // Store weights for the positive and negative taps.
                float weight = ComputeGaussian(i + 1);

                sampleWeights[i * 2 + 1] = weight;
                sampleWeights[i * 2 + 2] = weight;

                totalWeights += weight * 2;

                // To get the maximum amount of blurring from a limited number of
                // pixel shader samples, we take advantage of the bilinear filtering
                // hardware inside the texture fetch unit. If we position our texture
                // coordinates exactly halfway between two texels, the filtering unit
                // will average them for us, giving two samples for the price of one.
                // This allows us to step in units of two texels per sample, rather
                // than just one at a time. The 1.5 offset kicks things off by
                // positioning us nicely in between two texels.
                float sampleOffset = i * 2 + 1.5f;

                Vector2 delta = new Vector2(dx, dy) * sampleOffset;

                // Store texture coordinate offsets for the positive and negative taps.
                sampleOffsets[i * 2 + 1] = delta;
                sampleOffsets[i * 2 + 2] = -delta;
            }

            // Normalize the list of sample weightings, so they will always sum to one.
            for(int i = 0; i < sampleWeights.Length; i++) {
                sampleWeights[i] /= totalWeights;
            }

            // Tell the effect about our new filter settings.
            weightsParameter.SetValue(sampleWeights);
            offsetsParameter.SetValue(sampleOffsets);
        }


        /// <summary>
        /// Evaluates a single point on the gaussian falloff curve.
        /// Used for setting up the blur filter weightings.
        /// </summary>
        private float ComputeGaussian(float n) {
            float theta = blurAmount;

            return (float) ((1.0 / Math.Sqrt(2 * Math.PI * theta)) *
                           Math.Exp(-(n * n) / (2 * theta * theta)));
        }

    }
}
